import simpy

from osc05.domain.uncertainties.uncertainties import CraneUncertainty
from osc05.domain.uncertainties.weatherUncertainties import PrecipitationUncertainty
from osc05.static.constants import Commons


class Activity:
    def __init__(self, env, name, duration=10 * 5):
        self.duration = duration
        self.env = env
        self.name = name
        # keep the corresponding process object
        self.process = None

    def act(self):
        # keep the full duration to check an action is beginning or resuming.
        full_duration = self.duration
        # if the duration is not finished
        while self.duration:
            try:
                # identify the action is beginning or resuming
                action = " begins" if full_duration == self.duration else " resumes"
                # the time when the process begins or resumes
                start = self.env.now
                print("\t A:", self.name, " ", action, " at @", self.env.now)
                # try to pass the duration to
                # simulate the activity's progressing
                # before being interrupted.
                yield self.env.timeout(self.duration)
                # the duration is finished
                # after possible interruption
                self.duration = 0
                print(self.name, " ends   @", self.env.now)
            except simpy.Interrupt as interrupt:
                # get the interrupter
                interrupter = interrupt.cause
                time_to_resume = interrupter.get_time_to_resume()
                print("\t A:", interrupter.get_event_source_name(), "time to fix is ", time_to_resume,
                      "@", env.now)
                # the remaining time of the duration
                passed_time = self.env.now - start
                self.duration -= passed_time
                print("\t A:", self.name, " is interrupted by",
                      interrupter.get_event_source_name(),
                      " remaining time ",
                      self.duration,
                      " will back @ ",
                      self.env.now + time_to_resume)
                # waiting time to simulate repair time
                yield self.env.timeout(time_to_resume)
                # the current process is removed from being_interrupted_process_list
                # when the crane is fixed
                Commons.being_interrupted_process_list.remove(self.process)
        # activity is finished, remove it from main_process_list
        Commons.main_process_list.remove(self.process)


class Simulator:
    def __init__(self, env, activities):
        self.env = env
        self.activities = activities

    def simulate(self):
        for activity in self.activities:
            # process the act function
            p = self.env.process(activity.act())
            # activity keep pointer to its process
            # to remove the process from main_process_list and being_interrupted_process_list
            # when the interruption is over and activity is finished, respectively.
            activity.process = p
            Commons.main_process_list.append(p)
            yield p


# main
SIM_TIME = 10 * 1000  # 100 days
env = simpy.Environment()

activities = [
    Activity(env, "a1"),
    Activity(env, "a2"),
    Activity(env, "a3"),
    Activity(env, "a4"),
    Activity(env, "a5"),
    Activity(env, "a6"),
    Activity(env, "a7"),
    Activity(env, "a8"),
    Activity(env, "a9"),
    Activity(env, "a10"),
    Activity(env, "a11"),
    Activity(env, "a12"),
    Activity(env, "a13"),
    Activity(env, "a14"),
    Activity(env, "a15"),
    Activity(env, "a16"),
    Activity(env, "a17"),
    Activity(env, "a18")

]
# process the activities
simu = Simulator(env, activities)
env.process(simu.simulate())
# create the breaking object
crane_uncertainties = CraneUncertainty(env, break_mean=1 / 3000)
precipitationUncertainty = PrecipitationUncertainty(env)
env.run(until=SIM_TIME)
